using System;
using System.Collections.Generic;
using System.Text;

namespace Acms.DataControls
{
    public abstract class GenericDataSource<Item> : System.Web.UI.DataSourceControl
    {
        private GenericDataSourceView<Item> dataView;
        protected override System.Web.UI.DataSourceView GetView(string viewName)
        {
            if (dataView == null)
            {
                dataView = new GenericDataSourceView<Item>(this, viewName);
            }
            return dataView;
        }

        internal System.Collections.IEnumerable ExecuteSelect(System.Web.UI.DataSourceSelectArguments arguments)
        {
            if (SelectData != null)
            {
                this.LastSelectArgs = arguments;
                SelectEventArgs<Item> args = new SelectEventArgs<Item>(arguments);
                SelectData(this, args);
                arguments.TotalRowCount = args.TotalRecords;
                return args.GetData();
            }
            else
            {
                return null;
            }
        }

        protected System.Web.UI.DataSourceSelectArguments LastSelectArgs
        {
            get 
            {
                System.Web.UI.DataSourceSelectArguments tmp = new System.Web.UI.DataSourceSelectArguments();
                tmp.SortExpression = ViewState["LastSelectArgsSortExpression"] as string;
                tmp.MaximumRows = (int)ViewState["LastSelectArgsMaximumRows"];
                tmp.RetrieveTotalRowCount = (bool)ViewState["LastSelectArgsRetrieveTotalRowCount"];
                tmp.StartRowIndex = (int)ViewState["LastSelectArgsStartRowIndex"];
                return tmp;
            }
            set 
            {
                ViewState["LastSelectArgsSortExpression"] = value.SortExpression;
                ViewState["LastSelectArgsMaximumRows"] = value.MaximumRows;
                ViewState["LastSelectArgsRetrieveTotalRowCount"] = value.RetrieveTotalRowCount;
                ViewState["LastSelectArgsStartRowIndex"] = value.StartRowIndex;
            }
        }


        public event EventHandler<SelectEventArgs<Item>> SelectData;

        private bool enablePaging;
        public bool EnablePaging
        {
            get { return enablePaging; }
            set { enablePaging = value; }
        }

        public virtual bool CanDelete { get { return true; } }
        public virtual bool CanInsert { get { return true; } }
        public virtual bool CanUpdate { get { return true;} }

        internal int ExecuteInsert(System.Collections.IDictionary values)
        {
            if (BeforeUpdating != null)
            {
                BeforeUpdating(this, new BeforeUpdatingEventArgs(UpdateAction.Insert, null, values, null));
            }
            Item item = PopulateData(UpdateAction.Insert, null, values, null);
            if(UpdatingData != null)
            {
                UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Insert, null, values, null);
                UpdatingData(this, args);
            }
            int val = 0;
            if (UpdateData != null)
            {
                UpdateDataEventArgs<Item> args = new UpdateDataEventArgs<Item>(item, UpdateAction.Update, null, values, null);
                UpdateData(this, args);
                val = args.Changes;
            }
            else
            {
                val = SaveChanges(item);
            } 
            if (val > 0)
            {
                AddInsertedKey(item, values);
                if (UpdatedData != null)
                {
                    UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Insert, null, values, null);
                    UpdatedData(this, args);
                }
                RaiseDataSourceChangedEvent(EventArgs.Empty);
            }
            return val;
        }

        internal int ExecuteUpdate(System.Collections.IDictionary keys, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            if (BeforeUpdating != null)
            {
                BeforeUpdating(this, new BeforeUpdatingEventArgs(UpdateAction.Update, keys, values, oldValues));
            }
            Item item = PopulateData(UpdateAction.Update, keys, values, oldValues);
            if (UpdatingData != null)
            {
                UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Update, keys, values, oldValues);
                UpdatingData(this, args);
            }
            int val = 0;
            if (UpdateData != null)
            {
                UpdateDataEventArgs<Item> args = new UpdateDataEventArgs<Item>(item, UpdateAction.Update, keys, values, oldValues);
                UpdateData(this, args);
                val = args.Changes;
            }
            else
            {
                val = SaveChanges(item);
            }
            if (val > 0)
            {
                if (UpdatedData != null)
                {
                    UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Update, keys, values, oldValues);
                    UpdatedData(this, args);
                }
                RaiseDataSourceChangedEvent(EventArgs.Empty);
            }
            return val;
        }

        internal int ExecuteDelete(System.Collections.IDictionary keys, System.Collections.IDictionary oldValues)
        {
            if (BeforeUpdating != null)
            {
                BeforeUpdating(this, new BeforeUpdatingEventArgs(UpdateAction.Delete, keys, null, oldValues));
            }
            Item item = PopulateData(UpdateAction.Delete, keys, null, oldValues);
            if (UpdatingData != null)
            {
                UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Delete, keys, null, oldValues);
                UpdatingData(this, args);
            }
            int val = Delete(item);
            if (val > 0)
            {
                if (UpdatedData != null)
                {
                    UpdatingDataEventArgs<Item> args = new UpdatingDataEventArgs<Item>(item, UpdateAction.Delete, keys, null, oldValues);
                    UpdatedData(this, args);
                }
                RaiseDataSourceChangedEvent(EventArgs.Empty);
            }
            return val;
        }

        protected virtual Item PopulateData(UpdateAction action, System.Collections.IDictionary keys, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            return default(Item);
        }

        protected virtual int SaveChanges(Item item)
        {
            return 0;
        }



        protected virtual void AddInsertedKey(Item item, System.Collections.IDictionary values)
        {
        }

        protected virtual int Delete(Item item)
        {
            return 0;
        }
        
        public event EventHandler<BeforeUpdatingEventArgs> BeforeUpdating;

        public event EventHandler<UpdatingDataEventArgs<Item>> UpdatingData;

        public event EventHandler<UpdateDataEventArgs<Item>> UpdateData;

        public event EventHandler<UpdatingDataEventArgs<Item>> UpdatedData;

        internal static bool HasValue(string field, System.Collections.IDictionary items)
        {
            if (items != null && items.Contains(field))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        internal static T SetValue<T>(T existingValue, Enum field, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            return SetValue<T>(existingValue, field.ToString(), values, oldValues);
        }

        internal static T SetValue<T>(T existingValue, string field, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            if (RequiresUpdate<T>(field, values, oldValues))
            {
                return GetValue<T>(field, values);
            }
            else
            {
                return existingValue;
            }
        }

        internal static bool RequiresUpdate<T>(Enum field, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            return RequiresUpdate<T>(field.ToString(), values, oldValues);
        }

        internal static bool RequiresUpdate<T>(string field, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            if (values == null || !HasValue(field, values))
            {
                return false;
            }
            else if (oldValues == null)
            {
                return true;
            }
            else
            {
                return ValueChanged<T>(field, values, oldValues);
            }
        }

        internal static T GetValue<T>(Enum field, System.Collections.IDictionary items)
        {
            return GetValue<T>(field.ToString(), items);
        }

        internal static T GetValue<T>(string field, System.Collections.IDictionary items)
        {
            if (HasValue(field, items) && items[field] != null)
            {
                if (items[field.ToString()] is T)
                {
                    return (T)items[field];
                }
                else if (items[field] is string)
                {
                    string val = (string)items[field];
                    //if nullable type
                    if (string.IsNullOrEmpty(val))
                    {
                        T tmp = default(T);
                        if (tmp == null || tmp is Guid || tmp is DateTime)
                        {
                            return tmp;
                        }
                        else
                        {
                            throw new ApplicationException(string.Format("Field '{0}' in class '{1}' is null or empty but Value Type = '{2}' can not contain null", field, typeof(Item).FullName, typeof(T).ToString()));
                        }
                    }
                    else if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                    {
                        System.ComponentModel.NullableConverter nullableConverter = new System.ComponentModel.NullableConverter(typeof(T));
                        return (T)Convert.ChangeType(val, nullableConverter.UnderlyingType); ;
                    }
                    else
                    {
                        return (T)Convert.ChangeType(val, typeof(T));
                    }
                }
                else
                {
                    throw new ApplicationException(string.Format("Cound not convert value '{0}' of type '{1}' to type '{2}", items[field], items[field].GetType(), typeof(T)));
                }
            }
            else
            {
                return default(T);
            }
        }

        internal static bool ValueChanged<T>(string field, System.Collections.IDictionary newValues, System.Collections.IDictionary oldValues)
        {
            if (newValues == null || oldValues == null)
            {
                return false;
            }
            else if (HasValue(field, newValues) != HasValue(field, oldValues))
            {
                return true;
            }
            else
            {
                T newVal = GetValue<T>(field, newValues);
                T oldVal = GetValue<T>(field, oldValues);
                if (newVal == null && oldVal == null)
                {
                    return false;
                }
                else if (newVal == null && oldVal != null)
                {
                    return true;
                }
                else
                {
                    return !newVal.Equals(oldVal);
                }
            }
        }
    }

    public enum UpdateAction
    {
        Insert,
        Update,
        Delete
    }
}
